import { Module, OnModuleInit, RequestMethod } from '@nestjs/common';
import { CUSTOM_ROUTE_ARGS_METADATA, METHOD_METADATA, PARAMTYPES_METADATA, PATH_METADATA, ROUTE_ARGS_METADATA } from '@nestjs/common/constants';
import { DiscoveryService, MetadataScanner, ModulesContainer, NestContainer, Reflector } from '@nestjs/core';
import * as ts from 'typescript';
const fs = require('fs');
const path = require('path');

@Module({
	providers: [NestContainer, MetadataScanner, DiscoveryService]
})
export class GenerateSDKModule implements OnModuleInit{

	srcPath = path.join(__dirname, "../src")

	constructor(
		private readonly discoveryService: DiscoveryService,
		private readonly modulesContainer: ModulesContainer,
	) { }

	onModuleInit() {
		// console.time("生成SDK")
		// this.generate()
		// console.timeEnd("生成SDK")
	}

	//get(GenerateSDKModule).generate()
	generate() {
		let modules = ["AdminModule", "AppModule"]
		for (let index = 0; index < modules.length; index++) {
			let module = modules[index];
			this.discovery(module)
		}
	}
	discovery(module: string) {
		console.log("客户端SDK-TS生成开始")
		let allModules = [...this.modulesContainer.values()]
		let targetModule = allModules.find(item => item.name == module)
		let targetModuleArr = [...targetModule.imports]
		let flatTargetModuleArr = []
		for (let i = 0; i < targetModuleArr.length; i++) {
			let moduleItem = targetModuleArr[i];
			flatTargetModuleArr.push(moduleItem)
			flatTargetModuleArr.push([...moduleItem.imports])
		}
		flatTargetModuleArr = flatTargetModuleArr.flat()
		let controllers = this.discoveryService.getControllers(null, flatTargetModuleArr);
		let arr = []
		for (let i = 0; i < controllers.length; i++) {
			let controller = controllers[i];
			let controllerObj = {
				className: controller.name,
				import: new Set(),
				importFromCommon: new Set(),
				deps: new Set(),
				routes: []
			}
			let controllerPath = Reflect.getMetadata(PATH_METADATA, controller.instance.constructor)

			if (controllerPath.startsWith("admin/") || controllerPath.startsWith("app/")) {
				var recursionFindDeps = (className) => {
					let { content, type } = this.searchClass(this.srcPath, className)
					if (content.includes("extends")) {
						let regex = /extends\s+(\w+)/
						let match = content.match(regex);
						let extractedString = match ? match[1] : '';
						if (extractedString != "PartialType") {
							extractedString = type ? type + " " + extractedString : extractedString
							controllerObj.deps.add(extractedString)
						}
					}
					let importClass = this.getMemberTypeFromAST(content)
					for (let m = 0; m < importClass.length; m++) {
						let deps = importClass[m];
						let { content, type } = this.searchClass(this.srcPath, deps)
						deps = type ? type + " " + deps : deps
						controllerObj.deps.add(deps)
						recursionFindDeps(deps)
					}
				}
				let methods = Object.getOwnPropertyNames(controller.instance.constructor.prototype);
				for (let method of methods) {
					if (method != "constructor" && typeof controller.instance[method] === 'function') {
						let shouldSdk = Reflect.getMetadata("generateSDK", controller.instance[method])
						if (!shouldSdk) {
							continue;
						}
						let requestPath = path.posix.join(controllerPath, Reflect.getMetadata(PATH_METADATA, controller.instance[method]))
						if (requestPath.endsWith("/")) {
							requestPath = requestPath.slice(0, -1)
						}
						if (requestPath.includes(":")) {
							requestPath = requestPath.replace(/:([^/]+)/, '${$1}')
						}
						let requestMethod = RequestMethod[Reflect.getMetadata(METHOD_METADATA, controller.instance[method])]
						let requestName = method
						let route = {
							name: requestName,
							method: requestMethod,
							path: requestPath,
							pathParams: null,
							bodyParams: "",
							bodyType: null,
							returnType: "any",
							returnTypeClass: null,
							isList: false,
							desc: ""
						}
						let swagger = Reflect.getMetadata("swagger/apiOperation", controller.instance[method])
						route.desc = swagger?.summary
						let isList = Reflect.getMetadata("returnList", controller.instance[method])
						if (isList) {
							route.isList = isList
						}

						let bodyDecorator = Reflect.getMetadata(PARAMTYPES_METADATA, controller.instance, method)

						for (let k = 0; k < bodyDecorator.length; k++) {
							let func = bodyDecorator[k];
							if (func.name == "String") {
								let match = requestPath.match(/\${(.*?)}/);
								if (match) {
									route.pathParams = match[1] + ":string"
								}
							} else
								if (!["Object", "Number", "String", "Boolean", "Symbol", "Undefined", "Null"].includes(func.name)) {
									controllerObj.import.add(func.name)
									route.bodyType = func.name
									route.bodyParams = func.name
								}
						}

						let returnType = Reflect.getMetadata("returnInterface", controller.instance[method])
						if (returnType && returnType != "any") {
							route.returnType = returnType
							if( !returnType.startsWith("string") && !returnType.startsWith("number")){
								if(returnType.includes("[]")){
									returnType=returnType.replace("[]", "")
								}
								route.returnTypeClass = this.searchClass(this.srcPath, returnType)
								controllerObj.import.add(returnType)
							}
						}
						controllerObj.routes.push(route)
					}
				}
				for (let i = 0; i < [...controllerObj.import].length; i++) {
					const element = [...controllerObj.import][i];

					let { content, type } = this.searchClass(this.srcPath, element)

					if (content.includes("extends")) {
						let regex = /extends\s+(\w+)/
						let match = content.match(regex);
						let extractedString = match ? match[1] : '';
						if (extractedString != "PartialType") {
							extractedString = type ? type + " " + extractedString : extractedString
							controllerObj.deps.add(extractedString)
						}
					}
					let importClass = this.getMemberTypeFromAST(content)
					for (let m = 0; m < importClass.length; m++) {
						let deps = importClass[m];
						let { content, type } = this.searchClass(this.srcPath, deps)
						deps = type ? type + " " + deps : deps
						controllerObj.deps.add(deps)
						recursionFindDeps(deps)
					}
				}



				arr.push(controllerObj)
			}

		}
		this.writeFiles(arr, module)
	}

	searchClass(dir, pattern) {
		let res = ""
		let that = this
		function searchFiles(dir, pattern) {
			const files = fs.readdirSync(dir);
			for (const file of files) {
				const filePath = path.join(dir, file);
				const stat = fs.statSync(filePath);

				if (stat.isDirectory()) {
					searchFiles(filePath, pattern);
				} else if (stat.isFile() && file.endsWith('.ts')) {
					let content = fs.readFileSync(filePath, 'utf-8');
					let codeString = that.getCodeFromTsString(content, pattern)
					if (codeString) {
						codeString = codeString.replace(/(@ApiProperty\(\s*{[\s\S]*?\bdescription\b:\s*"([^"]*)"\s*[\s\S]*?}\))/g, '/**$2*/');
						codeString = codeString.replace(/@Type\(\(\) => .+?\)/g, "");
						codeString = codeString.replace(/@.*\([\s\S]*?\)\n*/gm, '');
						codeString = codeString.replace(/\)/gm, '');
						res = codeString
					}
				}
			}
		}
		searchFiles(dir, pattern)
		res = res.replace(/@.*\n/gm, '');
		let isClass = this.checkIsClassFromAST(res)
		let type = isClass[0] ? "" : "type"
		return { content: res, type }
	}

	writeFiles(arr, fileName) {
		if (arr.length == 0) return
		let commonDeps = new Set()
		let commonDepsArr = []
		for (let i = 0; i < arr.length; i++) {
			let group = arr[i];
			commonDepsArr.push(...group.deps)
		}
		commonDeps = new Set(commonDepsArr)
		let json = []

		for (let i = 0; i < arr.length; i++) {
			let group = arr[i];
			for (let k = 0; k < [...group.import].length; k++) {
				let importName = [...group.import][k];
				if (commonDepsArr.includes(importName)) {
					group.import.delete(importName)
					group.importFromCommon.add(importName)
				}
			}
			json.push(
				this.writeApiFile(group)
			)
			json.push(
				this.writeTypesFile(group)
			)
		}

		json.push(this.writeCommonTypesFile([...commonDeps]))
		let writePath = path.join(__dirname, "../static", fileName + ".json")
		// 获取目录路径
		const directory = path.dirname(writePath);
		try {
			fs.mkdirSync(directory, { recursive: true });
			fs.writeFileSync(writePath, JSON.stringify(json))
			console.log("客户端SDK-TS生成完毕")
		} catch (error) {
			console.log(error)
		}
	}

	writeApiFile(group) {
		let template = ``
		let importLine = `import http from '#HttpFolder#http';\nimport type {#import#} from './types'`
		if (group.importFromCommon.size > 0) {
			importLine += `\nimport type {${[...group.importFromCommon].join(" , ")}} from '../Common/types'`
		}
		let startLine = `
		\nexport default class #ModuleName#Api {
		`

		let name = group.className.replace("Controller", "")

		startLine = startLine.replace("#ModuleName#", name)
		if (group.import.size > 0) {
			template = importLine.replace("#import#", [...group.import].join(" , ")) + startLine
		} else {
			template = startLine
		}
		for (let k = 0; k < group.routes.length; k++) {
			let route = group.routes[k];

			let params = ""
			if (route.pathParams) {
				params = route.pathParams
			}
			if (route.bodyParams) {
				if (params) {
					params = [params, `params:${route.bodyType}`].join(",")
				} else {
					params = [`params:${route.bodyType}`].join(",")
				}

			}

			let responseWapper = "Axios.Response<#returnType#>"
			if (route.isList) {
				responseWapper = "Axios.List<#returnType#>"
			}

			responseWapper = responseWapper.replace("#returnType#", route.returnType)
			let paramsKey = "params"
			if (route.method == "POST") {
				paramsKey = "body"
			}
			template += `\n/**\n* ${route.desc}\n*/\nstatic ${route.name}(${params}): Promise<${responseWapper}> {
	return http.${route.method.toLowerCase()}(\`${route.path}\`,${params.includes("params") ? route.method.toLowerCase() == 'get' ? '{params:params}' : 'params' : '{}'});
}`
		}
		template += "\n}"

		return {
			folder: name,
			fileName: name + ".ts",
			content: template
		}
	}

	writeTypesFile(group) {
		let importLine = `import { #import# } from '../Common/types'`
		let template = ``
		if (group.deps.size > 0) {
			template = importLine.replace("#import#", [...group.deps].join(" , "))
		}
		for (let k = 0; k < [...group.import].length; k++) {
			let importName = [...group.import][k];
			let { content } = this.searchClass(this.srcPath, importName)
			template += '\n' + content
		}
		let name = group.className.replace("Controller", "")

		return {
			folder: name,
			fileName: "types.ts",
			content: template
		}
	}

	writeCommonTypesFile(arr: any[]) {
		let template = ``
		for (let i = 0; i < arr.length; i++) {
			let importName = arr[i].trim();
			if (importName.startsWith('type')) {
				importName = importName.replace('type ', '')
			}
			let { content } = this.searchClass(this.srcPath, importName)
			template += '\n' + content
		}

		return {
			folder: "Common",
			fileName: "types.ts",
			content: template
		}
	}

	getMemberTypeFromAST(code) {
		let importClass = []
		const sourceFile = ts.createSourceFile(
			'file.ts', // 文件名
			code,      // 文件内容
			ts.ScriptTarget.Latest, // 目标 ECMAScript 版本
			true       // 是否设置为严格模式
		);
		// 遍历 AST（抽象语法树）
		ts.forEachChild(sourceFile, (node) => {

			if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
				node.members.forEach((member) => {
					let propertyName = member.name.getText(sourceFile);
					let propertyType = member.type?.getText(sourceFile);
					if (propertyType == undefined) {
						// console.log(`属性名undefined: ${propertyName}`);
						propertyType = propertyName
					} else {
						propertyType = propertyType.replace("[]", "")
						if (propertyType != "string" && propertyType != "number" && propertyType != "string | number" && propertyType != "boolean" && propertyType != "Date" && propertyType != "any" && !propertyType.startsWith("{")) {
							importClass.push(propertyType)
						}
						if (propertyType.startsWith("{")) {
							let childDeps = this.getMemberTypeFromAST('interface Child ' + propertyType)
							importClass.push(...childDeps)
						}
					}
				});
			}
		});
		return importClass
	}

	checkIsClassFromAST(code) {
		let result = []
		const sourceFile = ts.createSourceFile(
			'file.ts', // 文件名
			code,      // 文件内容
			ts.ScriptTarget.Latest, // 目标 ECMAScript 版本
			true       // 是否设置为严格模式
		);
		// 遍历 AST（抽象语法树）
		ts.forEachChild(sourceFile, (node) => {
			result.push((ts.isClassDeclaration(node) || ts.isEnumDeclaration(node)))
		});
		return result
	}

	getCodeFromTsString(code: string, findName: string) {
		// 解析代码
		let sourceFile = ts.createSourceFile('test.ts', code, ts.ScriptTarget.ESNext, true);
		let codeString = ""
		// 遍历 AST，找到所有的 class 和 interface
		ts.forEachChild(sourceFile, node => {
			if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isEnumDeclaration(node)||ts.isTypeAliasDeclaration(node)) {
				if (node.name.text == findName) {
					codeString = node.getText()
				}
			}
		});
		// 格式化代码
		// let resFile = ts.createSourceFile(
		// 	'res.ts',
		// 	codeString,
		// 	ts.ScriptTarget.Latest,
		// 	true
		// );
		// let compilerOptions = ts.getDefaultCompilerOptions();
		// compilerOptions.indentSize = 4;
		// compilerOptions.tabSize = 4;
		// let printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed, ...compilerOptions })
		// let formattedCode = printer.printFile(resFile);
		return codeString
	}
}

